home *** CD-ROM | disk | FTP | other *** search
/ Delphi Magazine Collection 2001 / Delphi Magazine Collection 20001 (2001).iso / DISKS / Issue35 / RTTI / PropertyIO.pas next >
Encoding:
Pascal/Delphi Source File  |  1998-04-12  |  11.6 KB  |  384 lines

  1. unit PropertyIO;
  2.  
  3. interface
  4.  
  5. uses
  6.   Classes, SysUtils;
  7.  
  8. type
  9.  
  10.   {$TYPEINFO ON}
  11.   TSQLObject = class (TPersistent)
  12.   private
  13.     FPropertyList: TStringList;
  14.     function GetPropList: TStringList;
  15.     function GetSQLProperty (PropertyName: String): String;
  16.   protected
  17.     function TableName: String; virtual;
  18.     function SQLInsert: String;
  19.     function SQLUpdate: String;
  20.     function SQLSelect: String;
  21.     property PropertyList: TStringList read GetPropList;
  22.   public
  23.     destructor Destroy; override;
  24.   end;
  25.   {$TYPEINFO OFF}
  26.  
  27.   TPropertyImporter = class (TComponent)
  28.   private
  29.     FOnObjectImported: TNotifyEvent;
  30.   public
  31.     procedure ImportFile (FileName: String);
  32.   published
  33.     property OnObjectImported: TNotifyEvent read FOnObjectImported write FOnObjectImported;
  34.   end;
  35.  
  36.   EPropertyImporter = class (Exception);
  37.   
  38.   TPropertyExporter = class (TComponent)
  39.   private
  40.     FPropertyList: TStringList;
  41.     FFileName: String;
  42.     OutputStream: TFileStream;
  43.     procedure WriteLine (Text: String);
  44.     procedure SetFileName (Value: String);
  45.   public
  46.     constructor Create (AOwner: TComponent); override;
  47.     destructor Destroy; override;
  48.     procedure ExportObject (ThisObject: TObject);
  49.   published
  50.     property PropertyList: TStringList read FPropertyList;
  51.     property FileName: String read FFileName write SetFileName;
  52.   end;
  53.  
  54. procedure Register;
  55.  
  56. implementation
  57.  
  58. uses
  59.   TypInfo;
  60.  
  61. // unit procedures
  62.  
  63. procedure SetProperty (ThisObject: TObject; PropertyName, Value: String);
  64. var
  65.   PropertyPtr: PPropInfo;
  66. begin
  67.   // Get reference to property info.
  68.   PropertyPtr := GetPropInfo (ThisObject.ClassInfo, PropertyName);
  69.  
  70.   if PropertyPtr <> nil then begin
  71.     // RTTI available, set the property value if not read-only
  72.     if PropertyPtr^.SetProc <> nil then begin
  73.       // Determine property type and set value accordingly.
  74.       case PropertyPtr^.PropType^.Kind of
  75.         tkString, tkLString, tkChar: SetStrProp (ThisObject, PropertyPtr, Value);
  76.         tkEnumeration:               SetOrdProp (ThisObject, PropertyPtr, GetEnumValue (PropertyPtr^.PropType^, Value));
  77.         tkInteger:                   SetOrdProp (ThisObject, PropertyPtr, StrToInt (Value));
  78.         tkFloat:                     SetFloatProp (ThisObject, PropertyPtr, StrToFloat (Value));
  79.         tkVariant:                   SetVariantProp (ThisObject, PropertyPtr, Value);
  80.       else
  81.         raise EConvertError.Create ('unknown property type');
  82.       end;
  83.     end;
  84.   end;
  85. end;
  86.  
  87. function GetProperty (ThisObject: TObject; PropertyName: String): String;
  88. var
  89.   PropertyPtr : PPropInfo;
  90. begin
  91.   // supply default result in case property access fails
  92.   Result := '';
  93.   // get reference to property info.
  94.   PropertyPtr := GetPropInfo (ThisObject.ClassInfo, PropertyName);
  95.   if PropertyPtr <> nil then begin
  96.     // determine property type and return string accordingly.
  97.     case PropertyPtr^.PropType^.Kind of
  98.       tkString, tkLString:  Result := GetStrProp (ThisObject, PropertyPtr);
  99.       tkEnumeration:        Result := GetEnumName (PropertyPtr^.PropType^, GetOrdProp (ThisObject, PropertyPtr));
  100.       tkInteger:            Result := IntToStr (GetOrdProp (ThisObject, PropertyPtr));
  101.       tkChar:               Result := Char (GetOrdProp (ThisObject, PropertyPtr));
  102.       tkFloat:              Result := FloatToStr (GetFloatProp (ThisObject, PropertyPtr));
  103.       tkVariant:            Result := GetVariantProp (ThisObject, PropertyPtr);
  104.     end;
  105.   end;
  106. end;
  107.  
  108. procedure GetPropertyList (ThisObject: TObject; PropertyList: TStringList);
  109. var
  110.   ThisProperty: Integer;
  111.   PropertyCount: Integer;
  112.   PropList: PPropList;
  113. begin
  114.   // find out how many properties the object has
  115.   PropertyCount := GetTypeData (ThisObject.ClassInfo)^.PropCount;
  116.   // iterate through the properties collecting their names
  117.   if PropertyCount > 0 then begin
  118.     GetMem (PropList, PropertyCount * SizeOf (Pointer));
  119.     try
  120.       GetPropInfos (ThisObject.ClassInfo, PropList);
  121.       for ThisProperty := 0 to PropertyCount - 1 do begin
  122.         if IsStoredProp (ThisObject, GetPropInfo (ThisObject.ClassInfo, PropList^[ThisProperty]^.Name)) then begin
  123.           PropertyList.Add (PropList^[ThisProperty]^.Name);
  124.         end;
  125.       end;
  126.     finally
  127.       FreeMem (PropList, PropertyCount * SizeOf (Pointer));
  128.     end;
  129.   end;
  130. end;
  131.  
  132. function GetPropertyType (ThisObject: TObject; PropertyName: String): TTypeKind;
  133. var
  134.   PropertyPtr : PPropInfo;
  135. begin
  136.   // get reference to property info.
  137.   PropertyPtr := GetPropInfo (ThisObject.ClassInfo, PropertyName);
  138.   if PropertyPtr <> nil then begin
  139.     // determine property type and return string accordingly.
  140.     Result := PropertyPtr^.PropType^.Kind;
  141.   end else begin
  142.     Result := tkUnknown;
  143.   end;
  144. end;
  145.  
  146. // TPropertyExporter
  147.  
  148. constructor TPropertyExporter.Create (AOwner: TComponent);
  149. begin
  150.   inherited;
  151.   FPropertyList := TStringList.Create;
  152.   FFileName := 'Export.TXT';
  153. end;
  154.  
  155. destructor TPropertyExporter.Destroy;
  156. begin
  157.   OutputStream.Free;
  158.   FPropertyList.Free;
  159.   inherited;
  160. end;
  161.  
  162. procedure TPropertyExporter.SetFileName (Value: String);
  163. begin
  164.   OutputStream.Free;
  165.   OutputStream := nil;
  166.   FFileName := Value;
  167. end;
  168.  
  169. procedure TPropertyExporter.WriteLine (Text: String);
  170. const
  171.   CRLF = #13 + #10;
  172. begin
  173.   Text := Text + CRLF;
  174.   if OutputStream = nil then begin
  175.     OutputStream := TFileStream.Create (FileName, fmCreate or fmShareExclusive);
  176.   end;
  177.   OutputStream.Write (Text[1], Length (Text));
  178. end;
  179.  
  180. procedure TPropertyExporter.ExportObject (ThisObject: TObject);
  181. var
  182.   ThisProperty: Integer;
  183.   PropertyValue: String;
  184. begin
  185.   // populate the property list with all properties for the object if it is empty
  186.   if PropertyList.Count = 0 then begin
  187.     GetPropertyList (ThisObject, PropertyList);
  188.   end;
  189.   // now output the object, including the BEGIN and END delimiters
  190.   WriteLine ('BEGIN ' + ThisObject.ClassName);
  191.   for ThisProperty := 0 to PropertyList.Count - 1 do begin
  192.     PropertyValue := GetProperty (ThisObject, PropertyList[ThisProperty]);
  193.     WriteLine (PropertyList[ThisProperty] + '=' + PropertyValue);
  194.   end;
  195.   WriteLine ('END');
  196.   OutputStream.Free;
  197.   OutputStream := nil;
  198. end;
  199.  
  200. // TPropertyImporter
  201.  
  202. procedure TPropertyImporter.ImportFile (FileName: String);
  203. var
  204.   LineNumber: Integer;
  205.   InputFile: TextFile;
  206.  
  207.   function ReadLine: String;
  208.   begin
  209.     repeat
  210.       ReadLn (InputFile, Result);
  211.       Result := Trim (Result);
  212.       Inc (LineNumber);
  213.     until Result <> '';
  214.   end;
  215.  
  216. var
  217.   Text: String;
  218.   ThisClass: TPersistentClass;
  219.   ThisObject: TPersistent;
  220.   EndOfClass: Boolean;
  221.   PropName: String;
  222.   PropValue: String;
  223. begin
  224.   if not FileExists (FileName) then begin
  225.     raise EPropertyImporter.CreateFmt ('Error reading %s: file does not exist', [FileName]);
  226.   end;
  227.  
  228.   AssignFile (InputFile, FileName);
  229.   try
  230.     Reset (InputFile);
  231.  
  232.     while not EOF (InputFile) do begin
  233.       // read a line from the input file - it should be of the form "BEGIN <ClassName>"
  234.       Text := ReadLine;
  235.       LineNumber := 0;
  236.       if Pos ('BEGIN ', UpperCase (Text)) = 0 then begin
  237.         raise EPropertyImporter.CreateFmt ('Error reading %s at line %d: BEGIN <ClassName> expected.', [FileName, LineNumber]);
  238.       end;
  239.       // decode the class name
  240.       Text := Copy (Text, Pos (' ', Text) + 1, Length (Text));
  241.       // create a class of the correct type
  242.       ThisClass := GetClass (Text);
  243.       if ThisClass = nil then begin
  244.         raise EPropertyImporter.CreateFmt ('Error reading %s at line %d: class %s is not registered.', [FileName, LineNumber, Text]);
  245.       end;
  246.       ThisObject := ThisClass.Create;
  247.       try
  248.         // read in lines until we hit an "END"
  249.         EndOfClass := False;
  250.         repeat
  251.           Text := ReadLine;
  252.           if UpperCase (Text) = 'END' then begin
  253.             EndOfClass := True;
  254.           end else if Pos ('=', Text) = 0 then begin
  255.             raise EPropertyImporter.CreateFmt ('Error reading %s at line %d: "Name=Value" syntax expected.', [FileName, LineNumber]);
  256.           end else begin
  257.             // split the line at the "=" sign into a property name and a value
  258.             PropName := Copy (Text, 1, Pos ('=', Text) - 1);
  259.             PropValue := Copy (Text, Pos ('=', Text) + 1, Length (Text));
  260.             // set the property on the object
  261.             SetProperty (ThisObject, PropName, PropValue);
  262.           end;
  263.         until EndOfClass;
  264.         // if assigned, call the event to save the object
  265.         if Assigned (OnObjectImported) then begin
  266.           OnObjectImported (ThisObject);
  267.         end;
  268.       finally
  269.         ThisObject.Free;
  270.       end;
  271.     end;
  272.  
  273.   finally
  274.     CloseFile (InputFile);
  275.   end;
  276. end;
  277.  
  278. // TSQLObject
  279.  
  280. destructor TSQLObject.Destroy;
  281. begin
  282.   FPropertyList.Free;
  283.   inherited;
  284. end;
  285.  
  286. function TSQLObject.GetPropList: TStringList;
  287. begin
  288.   if FPropertyList = nil then begin
  289.     FPropertyList := TStringList.Create;
  290.     // obtain a list of all published properties in the class and ancestors
  291.     GetPropertyList (Self, FPropertyList);
  292.   end;
  293.   Result := FPropertyList;
  294. end;
  295.  
  296. function TSQLObject.TableName: String;
  297. begin
  298.   Result := Copy (ClassName, 2, Length (ClassName) - 1);
  299. end;
  300.  
  301. function TSQLObject.GetSQLProperty (PropertyName: String): String;
  302. begin
  303.   case GetPropertyType (Self, PropertyName) of
  304.     tkString,
  305.     tkLString,
  306.     tkChar:        Result := '"' + GetProperty (Self, PropertyName) + '"';
  307.     tkEnumeration: Result := IntToStr (GetOrdProp (Self, GetPropInfo (Self.ClassInfo, PropertyName)));
  308.     tkInteger,
  309.     tkFloat:       Result := GetProperty (Self, PropertyName);
  310.   else
  311.     Result := '';
  312.   end;
  313. end;
  314.  
  315. function TSQLObject.SQLInsert: String;
  316. var
  317.   Fields: String;
  318.   Values: String;
  319.   ThisValue: String;
  320.   ThisProp: Integer;
  321. begin
  322.   Fields := '';
  323.   Values := '';
  324.   for ThisProp := 0 to PropertyList.Count - 1 do begin
  325.     ThisValue := GetSQLProperty (PropertyList[ThisProp]);
  326.     if ThisValue <> '' then begin
  327.       Fields := Fields + ',' + PropertyList[ThisProp];
  328.       Values := Values + ',' + ThisValue;
  329.     end;
  330.   end;
  331.   // strip leading comma not required
  332.   Fields := Copy (Fields, 2, Length (Fields) - 1);
  333.   Values := Copy (Values, 2, Length (Values) - 1);
  334.   // build up the SQL text
  335.   Result := Format ('INSERT INTO %s (%s) VALUES (%s)', [TableName, Fields, Values]);
  336. end;
  337.  
  338. function TSQLObject.SQLUpdate: String;
  339. var
  340.   Fields: String;
  341.   ThisValue: String;
  342.   ThisProp: Integer;
  343. begin
  344.   Fields := '';
  345.   for ThisProp := 0 to PropertyList.Count - 1 do begin
  346.     ThisValue := GetSQLProperty (PropertyList[ThisProp]);
  347.     if ThisValue <> '' then begin
  348.       Fields := Fields + ',' + PropertyList[ThisProp] + '=' + ThisValue;
  349.     end;
  350.   end;
  351.   // strip leading comma not required
  352.   Fields := Copy (Fields, 2, Length (Fields) - 1);
  353.   // build up the SQL text
  354.   Result := Format ('UPDATE %s SET %s WHERE', [TableName, Fields]);
  355.   // you will need to append a way of identifying each class uniquely
  356. end;
  357.  
  358. function TSQLObject.SQLSelect: String;
  359. var
  360.   ThisProp: Integer;
  361.   Fields: String;
  362. begin
  363.   Fields := '';
  364.   for ThisProp := 0 to PropertyList.Count - 1 do begin
  365.     Fields := Fields + ',' + PropertyList[ThisProp];
  366.   end;
  367.   // strip leading comma not required
  368.   Fields := Copy (Fields, 2, Length (Fields) - 1);
  369.   // build up the SQL text
  370.   Result := Format ('SELECT %s FROM %s', [Fields, TableName]);
  371.   // after fetching the data from the database using the above command,
  372.   // you could cycle through the properties, fetching values from the cursor
  373.   // and using SetProperty to update the object
  374. end;
  375.  
  376. // unit procedures
  377.  
  378. procedure Register;
  379. begin
  380.   RegisterComponents ('Delphi Magazine', [TPropertyImporter, TPropertyExporter]);
  381. end;
  382.  
  383. end.
  384.